/**
 * @file console.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-04
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "dev/console.h"
#include "tools/kilb.h"
#include "comm/cpu_instr.h"
#include "dev/tty.h"
#include "cpu/irq.h"

#define CONSOLE_NR 8
static console_t console_buf[CONSOLE_NR];
static int curr_console_idx = 0;
/**
 * @brief 读取光标位置
 * @return * int 放回光标在页面的位置(0-1999)
 */
static int read_cursor_pos(void)
{
    
    int pos;
    irq_state_t state = irq_enter_protection();
    outb(0x3D4, 0x0F); // 访问低8位
    pos = inb(0x3D5);
    outb(0x3D4, 0x0E); // 访问高8位
    pos |= inb(0x3D5) << 8;
    irq_leave_protection(state);
    return pos;
}

/**
 * @brief 将指定终端的文本游标指定为光标的位置 update 手误
 *
 * @param console 终端编号
 * @return * int 放回完成写后的光标位置
 */
static int updata_cursor_pos(console_t *console)
{
    irq_state_t state = irq_enter_protection();
    uint16_t pos = (console - console_buf) * console->display_rows * console->display_cols;
    pos += console->cursor_row * console->display_cols + console->cursor_col;

    // uint16_t pos = (console - console_buf) * (console->cursor_row * console->display_cols + console->cursor_col);
    // uint16_t pos = console->cursor_row * console->display_cols + console->cursor_col;
    outb(0x3D4, 0x0F); // 写低地址
    outb(0x3D5, (uint8_t)(pos & 0xFF));
    outb(0x3D4, 0x0E); // 写高地址
    outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
    irq_leave_protection(state);
    return pos;
}
/**
 * @brief 指定console存光标
 */
void save_cursor(console_t *console)
{
    console->old_cursor_col = console->cursor_col;
    console->old_cursor_row = console->cursor_row;
}
/**
 * @brief 指定console取（恢复）光标
 */
void restore_cursor(console_t *console)
{
    console->cursor_col = console->old_cursor_col;
    console->cursor_row = console->old_cursor_row;
}

/**
 * @brief 到0行移动，回车
 */
static void move_to_col0(console_t *console)
{
    console->cursor_col = 0;
}
/**
 * @brief esc '[' 模式下移动光标到指定位置
 */
static void move_cursor(console_t *console)
{
    console->cursor_row = console->esc_param[0];
    console->cursor_col = console->esc_param[1];
}
/**
 * @brief 光标左移n位，超过最左边不换行处理
 */
static void move_left(console_t *console, int n)
{
    if (n == 0)
    {
        n = 1;
    }
    int col = console->cursor_col - n;
    console->cursor_col = (col >= 0) ? col : 0;
}
/**
 * @brief 光标右移n位，超过最右边不换行处理
 */
static void move_right(console_t *console, int n)
{
    if (n == 0)
    {
        n = 1;
    }
    int col = console->cursor_col + n;
    console->cursor_col = (col >= console->display_cols) ? console->display_cols - 1 : col;
}
/**
 * @brief 指定终端光标左移指定位置（回退）
 * @return * int 返回0成功 返回-1不成功
 */
static int move_backword(console_t *console, int n)
{
    int status = -1;
    for (int i = 0; i < n; i++)
    {
        // 判断光标在不在行首
        if (console->cursor_col > 0)
        {
            console->cursor_col--;
            status = 0;
        }
        else if (console->cursor_row > 0)
        {
            console->cursor_row--;
            console->cursor_col = console->display_cols - 1;
            status = 0;
        }
    }
    return status;
}
/**
 * @brief 调整光标
 *
 * @param console 控制台编号
 * @param n 光标移动的位置数
 * @return * void
 */
void move_forward(console_t *console, int n)
{
    /**
     * @brief 我们的光标的移动分为多步进行每次移动都看一看是否需要换行及换页
     */
    for (int i = 0; i < n; i++)
    {
        if (++console->cursor_col >= CONSOLE_COL_MAX)
        {
            console->cursor_col = 0;
            console->cursor_row++;
            // 还没对换页进行处理，及row满了
            if (console->cursor_row >= console->display_rows)
            {
                scroll_up(console, 1);
            }
        }
    }
}

/**
 * @brief 输出一个字符到控制台
 *  实际上我们直接修改0xb8000以上的现存，利用屏幕的定时刷新将修改的字符显示到屏幕上，我们
 *  不使用串口了已经
 * @param console 控制台编号
 * @param ch  输出字符
 * @return * void
 */
static void show_char(console_t *console, char ch)
{
    // 定义偏移
    int offset = console->cursor_col + console->cursor_row * console->display_cols;
    disp_char_t *p = console->disp_base + offset;

    p->c = ch;
    p->foreground = console->foreground;
    p->background = console->background;
    move_forward(console, 1);
}

/**
 * @brief 清除指定的范围内的行
 *
 * @param console 中断编号
 * @param start 开始行
 * @param end 结束含（该行同样会被清空）
 * @return * void
 */
static void erase_rows(console_t *console, int start, int end)
{
    disp_char_t *disp_start = console->disp_base + start * console->display_cols;
    disp_char_t *disp_end = console->disp_base + (end + 1) * console->display_cols;
    while (disp_start < disp_end)
    {
        disp_start->c = ' ';
        //前景色与背景色设置
        disp_start->foreground = console->foreground;
        disp_start->background = console->background;
        disp_start++;
    }
}
/**
 * @brief 指定console清屏
 */
static void erase_in_display(console_t *console)
{
    if (console->curr_param_index < 0)
    {
        return;
    }

    int param = console->esc_param[0];
    if (param == 2)
    {
        erase_rows(console, 0, console->display_cols - 1);
        console->cursor_col = console->cursor_row = 0;
    }
}

/**
 * @brief 删除一个字符， 我们删除一个字符，就是先左移光标后写空格再左移
 *
 * @param console
 * @return * staitc
 */
static void erase_backwork(console_t *console)
{
    if (move_backword(console, 1) == 0)
    {
        show_char(console, ' ');
        move_backword(console, 1);
    }
}

/**
 * @brief 上滚lines行
 */
void scroll_up(console_t *console, int lines)
{
    // dest目的地址  src 源地址  size 大小
    disp_char_t *dest = console->disp_base;
    disp_char_t *src = console->disp_base + (console->display_cols * lines);
    uint32_t size = (console->display_rows - lines) * console->display_cols * sizeof(disp_char_t);
    kernel_memcpy(dest, src, size);
    // 清空函数
    erase_rows(console, console->display_rows - lines, console->display_rows - 1);
    console->cursor_row -= lines;
}

/**
 * @brief 移动到下一行
 */
static void move_next_line(console_t *console)
{
    console->cursor_row++;
    if (console->cursor_row >= console->display_rows)
    {
        scroll_up(console, 1);
    }
}

/**
 * @brief specify console setting the font style
 */
static void set_font_style(console_t *console)
{
    static const color_t color_table[] = {
        COLOR_Black, COLOR_Red, COLOR_Green, COLOR_Yellow,  // 0-3
        COLOR_Blue, COLOR_Magenta, COLOR_Cyan, COLOR_White, // 4-7
    };
    // 根据参数设置前、背景色
    for (int i = 0; i <= console->curr_param_index; i++)
    {
        int param = console->esc_param[i];
        if ((param >= 30) && (param <= 37))
        {
            console->foreground = color_table[param - 30];
        }
        else if ((param >= 40) && (param <= 47))
        {
            console->background = color_table[param - 40];
        }
        else if ((param == 39))
        {
            console->foreground = COLOR_White;
        }
        else if (param == 49)
        {
            console->background = COLOR_Black;
        }
    }
}

/**
 * @brief 清空指定console的的esc参数列表
 */
static void clear_esc_param(console_t *console)
{
    kernel_memset(console->esc_param, 0, sizeof(console->esc_param));
    console->curr_param_index = 0;
}

/**
 * @brief 清屏操作
 */
static void console_clear(console_t *console)
{
    // size 一个屏幕的字节数
    int size = console->display_cols * console->display_rows;
    disp_char_t *start = console->disp_base;
    // 清屏
    for (int i = 0; i < size; i++, start++)
    {
        start->c = ' ';
        start->foreground = console->foreground;
        start->background = console->background;
    }
}

int console_init(int index)
{
    console_t *console = console_buf + index;

    // 求该控制台的地址
    console->disp_base = ((disp_char_t *)CONSOLE_DISP_ADDR) + index * (CONSOLE_ROW_MAX * CONSOLE_COL_MAX);
    console->display_rows = CONSOLE_ROW_MAX;
    console->display_cols = CONSOLE_COL_MAX;
    console->background = COLOR_Black;
    console->foreground = COLOR_White;
    // 初始化光标
    if (index==0)
    {
        int cursor_pos = read_cursor_pos();
        console->cursor_row = cursor_pos / console->display_cols;
        console->cursor_col = cursor_pos % console->display_cols;
    }else{
        console->cursor_row = 0;
        console->cursor_col = 0;
        console_clear(console);
        // updata_cursor_pos(console);
    }
    
   
    // 初始化久光标位置及状态
    console->old_cursor_col = console->cursor_col;
    console->old_cursor_row = console->cursor_row;
    console->write_state = CONSOLE_WRITE_NORMAL;
    mutex_init(&console->mutex);
}

/**
 * @brief 指定console填写esc加'['后的字符
 */
static void write_esc_square(console_t *console, char c)
{
    if ((c >= '0') && (c <= '9'))
    {
        int *param = &console->esc_param[console->curr_param_index];
        // 好好理解一下吧 两个字符，每次加一个字符要乘10
        *param = *param * 10 + c - '0';
    }
    else if ((c == ';') && (console->curr_param_index < ESC_PARAM_MAX))
    {
        console->curr_param_index++;
    }
    else
    {
        switch (c)
        {
        case 'm':
            set_font_style(console);
            break;
        case 'D':
            move_left(console, console->esc_param[0]);
            break;
        case 'C':
            move_right(console, console->esc_param[0]);
            break;
        case 'H':
        case 'f':
            move_cursor(console);
            break;
        case 'J':
            erase_in_display(console);
            break;
        default:
            break;
        }
        console->write_state = CONSOLE_WRITE_NORMAL;
    }
}

/**
 * @brief 指定console打印普通字符
 */
static void write_normal(console_t *console, char ch)
{
    switch (ch)
    {
    case ASCII_ESC:
        console->write_state = CONSOLE_WRITE_ESC;
        break;
    case '\n':
        // move_to_col0(console);
        move_next_line(console);
        break;
    case 0x7f:
        erase_backwork(console);
        break;
    case '\b':
        move_backword(console, 1);
        break;
    case '\r':
        move_to_col0(console);
        break;
    default:
        if ((ch >= ' ') && (ch <= '~'))
        {
            show_char(console, ch);
        }
        break;
    }
}
/**
 * @brief 指定console打印esc特殊字符后的字符
 */
static void write_esc(console_t *console, char ch)
{
    switch (ch)
    {
    case '7':
        // 存光标
        save_cursor(console);
        console->write_state = CONSOLE_WRITE_NORMAL;
        break;
    case '8':
        // 取光标
        restore_cursor(console);
        console->write_state = CONSOLE_WRITE_NORMAL;
        break;
    case '[':
        clear_esc_param(console);
        console->write_state = CONSOLE_WRITE_SQUARE;
        break;
    default:
        // 非 '7'  '8' 且换普通态
        console->write_state = CONSOLE_WRITE_NORMAL;
        break;
    }
}

/**
 * @brief 输出字符串的指定长度到指定的终端tty
 */
int console_write(tty_t *tty)
{
    console_t *c = console_buf + tty->console_index;
    int len=0;
    mutex_lock(&c->mutex);
    do{
        char ch;
        int err = tty_fifo_get(&tty->ofifo, &ch);
        if (err < 0)
        {
            break;
        }
        sem_notify(&tty->osem);
        switch (c->write_state)
        {
        case CONSOLE_WRITE_NORMAL:
            write_normal(c, ch);
            break;
        case CONSOLE_WRITE_ESC:
            write_esc(c, ch);
            break;
        case CONSOLE_WRITE_SQUARE:
            write_esc_square(c, ch);
            break;
        }
        len++;

    } while (1);
    mutex_unlock(&c->mutex);
    if (tty->console_index == curr_console_idx)
    {
        updata_cursor_pos(c);
        return len;
    }
    // updata_cursor_pos(c);
    // return 0;
}
void console_close(int console)
{
    return;
}

/**
 * @brief 处理tty的切换
 * @param idx tty编号
 */
void console_select(int idx)
{
    console_t *console = console_buf + idx;
    // 初始化，没有使用过就初始化，和显示器相关的寄存器
    if (console->disp_base == 0)
    {
        console_init(idx);
    }
    // 获取起始地址
    uint16_t pos = idx * console->display_rows * console->display_cols;
    // 0xc往高8位写
    outb(0x3D4, 0xc);
    outb(0x3D5, (uint8_t)(pos >> 8) & 0xff);
    // 0xc往高8位写
    outb(0x3D4, 0xD);
    outb(0x3D5, (uint8_t)(pos & 0xff));
    updata_cursor_pos(console);
    // 添加一个字符用于测试
    // char num = idx + '0';
    // curr_console_idx = idx;
    // show_char(console, num);
}